package com.ccf.business.source.service.impl;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.unit.DataSizeUtil;
import cn.hutool.core.net.URLEncoder;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ccf.business.source.listener.MultipartListenerResolver;
import com.ccf.business.source.model.vo.FolderVO;
import com.ccf.business.source.model.vo.UploadVO;
import com.ccf.business.source.service.ISourceService;
import com.google.common.collect.Lists;
import com.common.utils.constant.Constants;
import com.common.utils.tool.StringUtil;
import com.ccf.business.source.mapper.SourceMapper;
import com.ccf.business.source.model.Source;
import com.common.model.pojo.Query;
import com.common.model.result.CodeEnum;
import com.common.model.result.PageResult;
import com.service.mybatis.service.impl.SuperServiceImpl;
import com.service.mybatis.util.Condition;
import com.framework.minio.service.MinioService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 文件中心
 *
 * @author ccf
 * @date 2022-02-15 14:20:52
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class SourceServiceImpl extends SuperServiceImpl<SourceMapper, Source> implements ISourceService {

    private final MinioService minioService;

    /**
    * 列表
    * @param query
    * @return
    */
    @Override
    public PageResult<Source> findList(Query query){
        Page<Source> page = page(Condition.getPage(query), Condition.getQueryWrapper(query));
        return PageResult.<Source>builder().datas(page.getRecords()).code(CodeEnum.SUCCESS.getCode()).count(page.getTotal()).build();
    }

    @Override
    public List<FolderVO> folderList(String name) {
        List<Source> files = list(Wrappers.<Source>query().lambda()
                .eq(Source::getFileType, Source.FOLDER_TYPE)
                .like(StrUtil.isNotBlank(name), Source::getName, name)
                .orderByAsc(Source::getName));
        Map<String, List<Source>> pidMap =
                files.stream().collect(Collectors.groupingBy(Source::getPid));
        List<Source> sources = pidMap.get(Source.ROOT);
        List<FolderVO> folderVOS = addFolderVo(pidMap, sources);
        return folderVOS;
    }

    private List<FolderVO> addFolderVo(Map<String, List<Source>> pidMap, List<Source> sources) {
        List<FolderVO> list = Lists.newArrayList();
        if (sources == null){
            return list;
        }
        for (Source source : sources) {
            FolderVO folderVO = new FolderVO(source.getId(), source.getName());
            if (pidMap.containsKey(source.getId())){
                List<Source> children = pidMap.get(source.getId());
                List<FolderVO> childrenVos = addFolderVo(pidMap,  children);
                folderVO.setChildren(childrenVos);
            }
            list.add(folderVO);
        }
        return list;
    }


    @Override
    public List<Source> fileList(String pid, String name, String fileType, String fileTypes) {
        List<String> types = Lists.newArrayList();
        //做多类型查询
        if (StrUtil.isNotBlank(fileTypes)){
            String[] split = StrUtil.split(fileTypes, Constants.COMMA);
            types.addAll(Arrays.asList(split));
        }
        if (StrUtil.isNotBlank(fileType) && StrUtil.contains(Source.FOLDER_TYPE_STR,fileType)){
            fileType = Source.FOLDER_TYPE;
        }
        String[] pids = StrUtil.split(pid, Constants.COMMA);
        List<Source> files = list(Wrappers.<Source>query().lambda()
                .in(Source::getPid, pids)
                .in(CollUtil.isNotEmpty(types), Source::getFileType, types)
                .like(StrUtil.isNotBlank(name), Source::getName, name)
                .like(StrUtil.isNotBlank(fileType), Source::getFileType, fileType)
                .orderByDesc(Source::getCreateTime));
        for (Source source : files) {
            if (source.isDirectory()){
                source.setFileType(Source.FOLDER_TYPE_STR);
            }
        }
        return files;
    }

    @Override
    public List<UploadVO> uploadList() {
        List<UploadVO> list = new ArrayList<>();
        for (Map.Entry<String, Double> entry : MultipartListenerResolver.FILE_PROGRESS_MAP.entrySet()) {
            String key = entry.getKey();
            Double value = entry.getValue();
            list.add(UploadVO.builder()
                    .id(IdUtil.simpleUUID())
                    .name(key).status(
                            value.equals(MultipartListenerResolver.FAIL_DOUBLE)?"fail":"uploading")
                    .progress(value).build());

        }
        List<Source> files = list(Wrappers.<Source>query().lambda()
                .ne(Source::getFileType, Source.FOLDER_TYPE)
                .orderByAsc(Source::getCreateTime));
        List<UploadVO> result = files.stream().map(e -> UploadVO.builder()
                .id(e.getId())
                .name(e.getName())
                .status(e.getStatusCode())
                .progress(100.0)
                .build()).collect(Collectors.toList());
        list.addAll(result);
        return list;
    }

    @Override
    public void move(String[] idArray, String pid) {
        List<Source> fileList = new ArrayList<>();
        for (String id : idArray){
            Source file = getById(id);
            file.setPid(pid);
            fileList.add(file);
        }
        if (fileList.size() > 0){
            saveOrUpdateBatch(fileList);
        }
    }

    @Override
    public void saveOrRename(Source file) {
        //新增文件夹
        if (StrUtil.isBlank(file.getId())){
            file.setFileType(Source.FOLDER_TYPE);
            save(file);
        //重命名
        } else {
            Source byId = getById(file.getId());
            byId.setName(file.getName());
            saveOrUpdate(byId);
        }
    }

    @Override
    public void removeAndCacheByIds(String... ids) {
        for (String id : ids) {
            Source file = getById(id);
            String fileType = file.getFileType();
            //文件夹递归删除
            if (file.isDirectory()){
                List<Source> files = list(Wrappers.<Source>query().lambda().eq(Source::getPid, id));
                List<String> cids = files.stream().map(e -> e.getId()).collect(Collectors.toList());
                removeAndCacheByIds(cids.toArray(new String[0]));
            } else {
                String path = file.getPath();
                if (StrUtil.isNotBlank(path)){
                    minioService.removeFile(path);
                }
            }
            removeById(id);
        }
    }

    @Override
    public File packageToDir(String id) {
        String rootDir = "/tmp/download/";
        FileUtil.mkdir(rootDir);
        if (StrUtil.contains(id, ",")){
            String[] ids = StrUtil.split(id, ",");
            String commonDir = rootDir + "files" + File.separator;
            File mkdir = FileUtil.mkdir(commonDir);
            for (String ida : ids) {
                copySource(ida, commonDir);
            }
            return mkdir;
        } else {
            return copySource(id, rootDir);
        }
    }

    /**
     * 打包单个文件
     * @param id
     * @param rootDir
     * @return
     */
    private File copySource(String id, String rootDir) {
        File dir = null;
        Source byId = getById(id);
        String name = byId.getName();
        if (byId.isDirectory()){
            dir = copyDir(id, rootDir + name + File.separator);
        } else {
            String path = byId.getPath();
            String fullPath = rootDir + name;
            try {
                InputStream fileStream = minioService.getFileStream(path);
                dir = FileUtil.writeFromStream(fileStream, fullPath);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return dir;
    }

    /**
     * copy文件夹内容
     * @param id
     * @param parentDir 父目录
     * @return
     */
    private File copyDir(String id, String parentDir) {
        File mkdir = FileUtil.mkdir(parentDir);
        List<Source> files = list(Wrappers.<Source>query().lambda().eq(Source::getPid, id));
        for(Source source : files) {
            if (source.isDirectory()){
                copyDir(source.getId(), parentDir + source.getName() + File.separator );
            } else {
                String fullPath = parentDir +  source.getName();
                String path = source.getPath();
                try {
                    InputStream fileStream = minioService.getFileStream(path);
                    FileUtil.writeFromStream(fileStream, fullPath);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return mkdir;
    }

    @Value("${file.view-prefix}")
    private String viewPrefix;

    @Autowired
    private HttpServletRequest request;
    /**
     * 获取请求头
     *
     * @return
     */
    public String getHeader() {
        //获取当前请求地址 nginx代理后 这些地址发生改变
        String header = request.getScheme() + Constants.HEADER +
                request.getServerName() + Constants.COLON + request.getServerPort() +
                request.getContextPath();
        log.info(header);
        return header;
    }

    @Override
    public Source detail(String id) {
        Source source = getById(id);
        if (source == null){
            throw new RuntimeException("id不存在");
        }
        source.setViewPath(StrUtil.format(viewPrefix,
                URLEncoder.createDefault().encode(
                        Base64.encode(
                                minioService.getEndpoint() + source.getPath() +
                                "?response-content-type=application/octet-stream"),
                        Charset.forName("UTF-8"))));
        String pid = source.getPid();
        if (Source.ROOT.equalsIgnoreCase(pid)){
            source.setClassify(Source.ALL);
        } else {
            setClassify(source, pid);
        }
        return source;
    }

    /**
     * 设置分类
     * @param source
     * @param pid
     */
    private void setClassify(Source source, String pid) {
        Source folder = getById(pid);
        if (StrUtil.isNotBlank(folder.getClassify())){
            source.setClassify(folder.getName()+ "/" + folder.getClassify());
        } else {
            source.setClassify(folder.getName());
        }
        String newPid = folder.getPid();
        if (Source.ROOT.equalsIgnoreCase(newPid)){
            return;
        } else {
            setClassify(source, newPid);
        }

    }

    @Override
    public void upload(MultipartFile[] files, String pid) throws Exception {
        List<Source> sources = new ArrayList<>();
        if (files == null){
            throw new RuntimeException("未获取上传文件");
        }
        for (MultipartFile file : files) {
            if (file == null){
                continue;
            }
            Source source = new Source();
            source.setPid(pid);
            String originalFilename = file.getOriginalFilename();
            String ext = FileUtil.extName(originalFilename);
            source.setFileType(StringUtil.lowerCase(ext));
            source.setName(originalFilename);
            source.setFileSize(DataSizeUtil.format(file.getSize()));
            InputStream inputStream = file.getInputStream();
            String path = minioService.putSourceFile(inputStream, ext);
            source.setPath(path);
            source.setUploadStatus(Source.UPLOAD_SUCCESS);
            sources.add(source);
            //清除上传中的状态
            MultipartListenerResolver.clear(originalFilename);
        }
        if (sources.size() > 0){
            saveBatch(sources);
        }
    }

}
